package com.ks.winterwarmimage.mqtt.service;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.PowerManager;
import android.util.Log;

import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttPingSender;
import org.eclipse.paho.client.mqttv3.internal.ClientComms;

/**
 * Android上默认的ping发送者实现。它基于AlarmManager。
 *
 * <p>
 * 此类实现了{@link MqttPingSender}ping接口，允许应用程序在每个保持活动间隔向服务器发送ping数据包。
 * </p>
 *
 * @see MqttPingSender
 */
class AlarmPingSender implements MqttPingSender {
   // 用于Intent、日志消息等的标识符
   private static final String TAG = "AlarmPingSender";

   // TODO: Add log.
   private ClientComms comms;
   private MqttService service;
   private BroadcastReceiver alarmReceiver;
   private AlarmPingSender that;
   private PendingIntent pendingIntent;
   private volatile boolean hasStarted = false;

   public AlarmPingSender(MqttService service) {
      if (service == null) {
         throw new IllegalArgumentException("Neither service nor client can be null.");
      }
      this.service = service;
      that = this;
   }

   @Override
   public void init(ClientComms comms) {
      this.comms = comms;
      this.alarmReceiver = new AlarmReceiver();
   }

   @Override
   public void start() {
      String action = MqttServiceConstants.PING_SENDER + comms.getClient().getClientId();
      Log.d(TAG, "Register alarmreceiver to MqttService"+ action);
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
         service.registerReceiver(alarmReceiver, new IntentFilter(action), Context.RECEIVER_EXPORTED);
         pendingIntent = PendingIntent.getBroadcast(service, 0, new Intent(action), PendingIntent.FLAG_IMMUTABLE);
      }else{
         service.registerReceiver(alarmReceiver, new IntentFilter(action));
         pendingIntent = PendingIntent.getBroadcast(service, 0, new Intent(action), PendingIntent.FLAG_UPDATE_CURRENT);
      }
      schedule(comms.getKeepAlive());
      hasStarted = true;
   }

   @Override
   public void stop() {

      Log.d(TAG, "Unregister alarmreceiver to MqttService"+comms.getClient().getClientId());
      if(hasStarted){
         if(pendingIntent != null){
            // 取消定时任务
            AlarmManager alarmManager = (AlarmManager) service.getSystemService(Service.ALARM_SERVICE);
            alarmManager.cancel(pendingIntent);
         }

         hasStarted = false;
         try{
            service.unregisterReceiver(alarmReceiver);
         }catch(IllegalArgumentException e){
            // 忽略取消注册错误
         }
      }
   }

   @Override
   public void schedule(long delayInMilliseconds) {
      long nextAlarmInMilliseconds = System.currentTimeMillis()  + delayInMilliseconds;
      Log.d(TAG, "Schedule next alarm at " + nextAlarmInMilliseconds);
      AlarmManager alarmManager = (AlarmManager) service .getSystemService(Service.ALARM_SERVICE);

      if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
         // 该注释说明在Android 6.0（API 23）及以上版本中，当设备进入低功耗休眠模式（Doze）时，
         // 常规的setExact定时器会失效，而setExactAndAllowWhileIdle方法可以强制系统在休眠期间执行定时任务。
         Log.d(TAG, "Alarm scheule using setExactAndAllowWhileIdle, next: " + delayInMilliseconds);
         alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,  pendingIntent);
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
         Log.d(TAG, "Alarm scheule using setExact, delay: " + delayInMilliseconds);
         alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,  pendingIntent);
      } else {
         alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,  pendingIntent);
      }
   }

   /**
    * 此类负责向MQTT代理发送PingReq数据包
    */
   class AlarmReceiver extends BroadcastReceiver {
      private PowerManager.WakeLock wakelock;
      private final String wakeLockTag = MqttServiceConstants.PING_WAKELOCK
              + that.comms.getClient().getClientId();

      @Override
      @SuppressLint("Wakelock")
      public void onReceive(Context context, Intent intent) {
         // 根据文档说明："只要AlarmReceiver的onReceive()方法正在执行，
         // AlarmManager就会持有CPU唤醒锁。这保证了在广播处理完成前设备不会休眠"
         // 但这里仍需要获取唤醒锁来等待ping操作完成

         Log.d(TAG, "Sending Ping at:" + System.currentTimeMillis());

         PowerManager pm = (PowerManager) service
                 .getSystemService(Service.POWER_SERVICE);
         wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
         wakelock.acquire();

         // 为令牌分配新的回调，以便在PingResq到达后执行代码。获取另一个尾锁，即使接收器已经有了，也要释放它，直到ping响应返回。
         IMqttToken token = comms.checkForActivity(new IMqttActionListener() {

            @Override
            public void onSuccess(IMqttToken asyncActionToken) {
               Log.d(TAG, "Success. Release lock(" + wakeLockTag + "):"
                       + System.currentTimeMillis());
               //完成后释放唤醒锁
               wakelock.release();
            }
            @Override
            public void onFailure(IMqttToken asyncActionToken,
                                  Throwable exception) {
               Log.d(TAG, "Failure. Release lock(" + wakeLockTag + "):"
                       + System.currentTimeMillis());
               //完成后释放唤醒锁
               wakelock.release();
            }
         });

         if (token == null && wakelock.isHeld()) {
            wakelock.release();
         }
      }
   }
}
